/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is Forte for Java, Community Edition. The Initial
* Developer of the Original Code is Sun Microsystems, Inc. Portions
* Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.vcs.cmdline.commands;
import org.netbeans.modules.vcs.cmdline.*;
import org.netbeans.modules.vcs.util.*;
import org.netbeans.modules.vcs.VcsFileSystem;
import org.netbeans.modules.vcs.cmdline.exec.*;
import org.openide.nodes.*;
import org.openide.loaders.DataObject;
import org.openide.filesystems.FileObject;
import org.openide.cookies.*;
import java.util.*;
import java.io.*;
import java.net.URL;
import javax.swing.*;
import javax.swing.text.*;
/**
*
* @author Martin Entlicher
* @version
*/
public class CvsDiff extends VcsAdditionalCommand implements RegexListener {
private Debug E=new Debug("CvsDiff",true); // NOI18N
private Debug D=E;
//private static transient String TMP_ROOT="vcs/tmp"; // NOI18N
private transient String TMP_ROOT;
private File tmpDir = null;
private File tmpDir2 = null;
private String tmpDirName = ""; // NOI18N
private String tmpDir2Name = ""; // NOI18N
/**
* @associates String
*/
Hashtable vars = null;
private String rootDir = null;
private String rootDirWroot = null;
private String dir = null;
//private String mdir = null;
private String file = null;
private String diffDataRegex = null;
private NoRegexListener stdoutNRListener = null;
private NoRegexListener stderrNRListener = null;
private RegexListener stdoutListener = null;
private RegexListener stderrListener = null;
private String dataRegex = null;
private String errorRegex = null;
private String checkoutCmd = null;
private String diffCmd = null;
private StringBuffer diffBuffer = new StringBuffer(4096);
/**
* @associates DiffAction
*/
private Vector diffActions = new Vector();
private CvsDiffFrame diffFrame = null;
private static int currentDiffLine = 0;
private static final java.awt.Color colorMissing = new java.awt.Color(255, 160, 180);
private static final java.awt.Color colorAdded = new java.awt.Color(180, 255, 180);
private static final java.awt.Color colorChanged = new java.awt.Color(160, 200, 255);
//private JEditorPane e1;
//private JEditorPane e2;
/** Creates new CvsDiff */
public CvsDiff() {
}
private File createTMP() {
TMP_ROOT=System.getProperty("netbeans.user")+File.separator+
"system"+File.separator+"vcs"+File.separator+"tmp";
File tmpDir = new File(TMP_ROOT);
if (!tmpDir.exists()) {
tmpDir.mkdirs();
}
long tmpId;
do {
tmpId = 10000 * (1 + Math.round (Math.random () * 8)) + Math.round (Math.random () * 1000);
} while (new File(TMP_ROOT+File.separator+"tmp"+tmpId).exists()); // NOI18N
TMP_ROOT = TMP_ROOT+File.separator+"tmp"+tmpId; // NOI18N
tmpDir = new File(TMP_ROOT);
if (!tmpDir.exists()) {
tmpDir.mkdirs();
}
return tmpDir;
}
private boolean checkOut(Hashtable vars, String file, String revision, String tmpDir) {
String cmd = checkoutCmd;
String varRevision = ""; // NOI18N
//if (revision != null) varRevision = " -r "+revision+" "; // NOI18N
if (revision != null) varRevision = ""+revision; // NOI18N
vars.put("REVISION", varRevision); // NOI18N
D.deb("varRevision = "+varRevision); // NOI18N
vars.put("TEMPDIR", tmpDir); // NOI18N
D.deb("checkOut Command: "+cmd); // NOI18N
Variables v=new Variables();
String prepared=v.expand(vars,cmd, true);
D.deb("checkOut prepared: "+prepared); // NOI18N
if (stderrListener != null) {
String[] command = { "CHECKOUT: "+prepared }; // NOI18N
stderrListener.match(command);
}
if (stderrNRListener != null) stderrNRListener.match("CHECKOUT: "+prepared); // NOI18N
ExternalCommand ec=new ExternalCommand(prepared);
ec.setTimeout(((Long) vars.get("TIMEOUT")).longValue()); // NOI18N
if (this.stdoutNRListener != null) ec.addStdoutNoRegexListener(this.stdoutNRListener);
if (this.stderrNRListener != null) ec.addStderrNoRegexListener(this.stderrNRListener);
if (this.stdoutListener != null) {
try {
ec.addStdoutRegexListener(this.stdoutListener, this.dataRegex);
} catch (BadRegexException e) {
if (stderrListener != null) {
String[] elements = { "CHECKOUT: Bad data regex "+dataRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
}
}
if (this.stderrListener != null) {
try {
ec.addStderrRegexListener(this.stderrListener, this.errorRegex);
} catch (BadRegexException e) {
String[] elements = { "CHECKOUT: Bad error regex "+errorRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
}
if ( ec.exec() != ExternalCommand.SUCCESS ){
E.err("exec failed "+ec.getExitStatus()); // NOI18N
return false;
} else return true;
}
private boolean performDiff(String revision1, String revision2) {
String cmd = diffCmd;
String varRevision = ""; // NOI18N
if (revision1 != null) varRevision += " -r "+revision1+" "; // NOI18N
if (revision2 != null) varRevision += " -r "+revision2+" "; // NOI18N
vars.put("REVISION", varRevision); // NOI18N
D.deb("diff command: "+cmd); // NOI18N
Variables v=new Variables();
String prepared=v.expand(vars,cmd, true);
D.deb("diff prepared: "+prepared); // NOI18N
if (stderrListener != null) {
String[] command = { "DIFF: "+prepared }; // NOI18N
stderrListener.match(command);
}
if (stderrNRListener != null) stderrNRListener.match("DIFF: "+prepared); // NOI18N
ExternalCommand ec=new ExternalCommand(prepared);
ec.setTimeout(((Long) vars.get("TIMEOUT")).longValue()); // NOI18N
try{
D.deb("stdout diff dataRegex = "+diffDataRegex); // NOI18N
ec.addStdoutRegexListener(this,diffDataRegex);
} catch (BadRegexException e) {
if (stderrListener != null) {
String[] elements = { "cvs diff: Bad data regex "+diffDataRegex }; // NOI18N
stderrListener.match(elements);
}
return false;
}
if (this.stdoutNRListener != null) ec.addStdoutNoRegexListener(stdoutNRListener);
if (this.stderrNRListener != null) ec.addStderrNoRegexListener(stderrNRListener);
if (this.stdoutListener != null) {
try {
ec.addStdoutRegexListener(this.stdoutListener, this.dataRegex);
} catch (BadRegexException e) {
if (stderrListener != null) {
String[] elements = { "DIFF: Bad data regex "+dataRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
}
}
if (this.stderrListener != null) {
try {
ec.addStderrRegexListener(this.stderrListener, this.errorRegex);
} catch (BadRegexException e) {
String[] elements = { "DIFF: Bad error regex "+errorRegex+"\n" }; // NOI18N
stderrListener.match(elements);
}
}
if ( ec.exec() != ExternalCommand.SUCCESS ){
D.deb("exec failed "+ec.getExitStatus()); // NOI18N
return false;
}
return true;
}
private boolean openEditors(String file1, String file2, String title1, String title2) {
String mime = (String) vars.get("MIMETYPE"); // NOI18N
D.deb("I have MIME = "+mime); // NOI18N
URL url1 = null;
URL url2 = null;
try {
url1 = new File(file1).toURL();
url2 = new File(file2).toURL();
} catch (java.net.MalformedURLException e) {
D.deb("MalformedURLException "+e.getMessage()); // NOI18N
return false;
}
diffFrame = new CvsDiffFrame(this);
MiscStuff.centerWindow(diffFrame);
if (mime != null) {
diffFrame.setMimeType1(mime);
diffFrame.setMimeType2(mime);
}
try {
diffFrame.setFile1(url1);
diffFrame.setFile2(url2);
} catch (IOException e) {
D.err("IO Exception "+e.getMessage()); // NOI18N
return false;
}
diffFrame.setFile1Title(title1);
diffFrame.setFile2Title(title2);
diffFrame.pack();
diffFrame.show();
return true;
}
private void insertEmptyLines() {
int n = diffActions.size();
int ins1 = 0;
int ins2 = 0;
D.deb("insertEmptyLines():"); // NOI18N
for(int i = 0; i < n; i++) {
DiffAction action = (DiffAction) diffActions.get(i);
int n1 = action.getF1Line1() + ins1;
int n2 = action.getF1Line2() + ins1;
int n3 = action.getF2Line1() + ins2;
int n4 = action.getF2Line2() + ins2;
D.deb("Action: "+action.getAction()+": ("+n1+","+n2+","+n3+","+n4+")"); // NOI18N
D.deb("ins1 = "+ins1+", ins2 = "+ins2); // NOI18N
switch (action.getAction()) {
case DiffAction.DELETE:
diffFrame.addEmptyLines2(n3, n2 - n1 + 1);
ins2 += n2 - n1 + 1;
break;
case DiffAction.ADD:
diffFrame.addEmptyLines1(n1, n4 - n3 + 1);
ins1 += n4 - n3 + 1;
break;
case DiffAction.CHANGE:
int r1 = n2 - n1;
int r2 = n4 - n3;
if (r1 < r2) {
diffFrame.addEmptyLines1(n2, r2 - r1);
ins1 += r2 - r1;
} else if (r1 > r2) {
diffFrame.addEmptyLines2(n4, r1 - r2);
ins2 += r1 - r2;
}
break;
}
action.setF1Line1(n1);
action.setF1Line2(n2);
action.setF2Line1(n3);
action.setF2Line2(n4);
}
}
private void setDiffHighlight(boolean set) {
int n = diffActions.size();
D.deb("Num Actions = "+n); // NOI18N
for(int i = 0; i < n; i++) {
DiffAction action = (DiffAction) diffActions.get(i);
int n1 = action.getF1Line1();
int n2 = action.getF1Line2();
int n3 = action.getF2Line1();
int n4 = action.getF2Line2();
D.deb("Action: "+action.getAction()+": ("+n1+","+n2+","+n3+","+n4+")"); // NOI18N
switch (action.getAction()) {
case DiffAction.DELETE:
if (set) diffFrame.highlightRegion1(n1, n2, colorMissing);
else diffFrame.highlightRegion1(n1, n2, java.awt.Color.white);
break;
case DiffAction.ADD:
if (set) diffFrame.highlightRegion2(n3, n4, colorAdded);
else diffFrame.highlightRegion2(n3, n4, java.awt.Color.white);
break;
case DiffAction.CHANGE:
if (set) {
diffFrame.highlightRegion1(n1, n2, colorChanged);
diffFrame.highlightRegion2(n3, n4, colorChanged);
} else {
diffFrame.highlightRegion1(n1, n2, java.awt.Color.white);
diffFrame.highlightRegion2(n3, n4, java.awt.Color.white);
}
break;
}
}
}
/**
* Executes the checkout and diff commands and display differences.
* @param vars variables needed to run cvs commands
* @param args the arguments, first two are supposed to be the revision tags to be compared.
* @param stdoutNRListener listener of the standard output of the command
* @param stderrNRListener listener of the error output of the command
* @param stdoutListener listener of the standard output of the command which
* satisfies regex <CODE>dataRegex</CODE>
* @param dataRegex the regular expression for parsing the standard output
* @param stderrListener listener of the error output of the command which
* satisfies regex <CODE>errorRegex</CODE>
* @param errorRegex the regular expression for parsing the error output
* @return true if the command was succesfull,
* false if some error has occured.
*/
public boolean exec(Hashtable vars, String[] args,
NoRegexListener stdoutNRListener, NoRegexListener stderrNRListener,
RegexListener stdoutListener, String dataRegex,
RegexListener stderrListener, String errorRegex) {
boolean status = true;
this.stdoutNRListener = stdoutNRListener;
this.stderrNRListener = stderrNRListener;
this.stdoutListener = stdoutListener;
this.dataRegex = dataRegex;
this.stderrListener = stderrListener;
this.errorRegex = errorRegex;
this.vars = vars;
int arglen = args.length;
if (arglen < 2) {
String message = "Too few arguments to Diff command !"; // NOI18N
String[] elements = { message };
if (stderrListener != null) stderrListener.match(elements);
if (stderrNRListener != null) stderrNRListener.match(message);
return false;
}
this.checkoutCmd = args[arglen - 2];
this.diffCmd = args[arglen - 1];
String mime = (String) vars.get("MIMETYPE"); // NOI18N
if (mime == null || mime.indexOf("unknown") >= 0) { // NOI18N
String message = org.openide.util.NbBundle.getBundle(CvsDiff.class).getString("CvsDiff.unknownMIMETYPE");
String[] elements = { message };
if (stderrListener != null) stderrListener.match(elements);
if (stderrNRListener != null) stderrNRListener.match(message);
return false;
}
this.rootDir = (String) vars.get("ROOTDIR"); // NOI18N
String module = (String) vars.get("MODULE"); // NOI18N
if (module == null) module = ""; // NOI18N
if (module.length() > 0) module += File.separator;
this.rootDirWroot = VcsFileSystem.substractRootDir(rootDir, module);
D.deb("rootDir = "+rootDir+", module = "+module+" => rootDirWroot = "+rootDirWroot); // NOI18N
//this.dir = (String) vars.get("DIR"); // NOI18N
this.dir = module + (String) vars.get("DIR"); // NOI18N
this.file = (String) vars.get("FILE"); // NOI18N
tmpDir = createTMP();
//tmpDirName = tmpDir.getName();
tmpDirName = tmpDir.getAbsolutePath();
String path = rootDir+File.separator+dir+File.separator+file;
this.diffDataRegex = (String) vars.get("DATAREGEX"); // NOI18N
if (this.diffDataRegex == null) this.diffDataRegex = "(^.*)$"; // NOI18N
this.diffDataRegex = "(^[0-9]+(,[0-9]+|)[d][0-9]+$)|(^[0-9]+(,[0-9]+|)[c][0-9]+(,[0-9]+|)$)|(^[0-9]+[a][0-9]+(,[0-9]+|)$)"; // NOI18N
String revision1 = null;
String revision2 = null;
if (args != null && args.length > 2) {
revision1 = args[0];
if (args.length > 3) {
revision2 = args[1];
tmpDir2 = createTMP();
tmpDir2Name = tmpDir2.getAbsolutePath();
}
}
status = checkOut(vars, dir+File.separator+file, revision1, tmpDirName);
if (!status) {
close();
return status;
}
if (revision2 != null) {
status = checkOut(vars, dir+File.separator+file, revision2, tmpDir2Name);
if (!status) {
close();
return status;
}
}
performDiff(revision1, revision2);
final String file1Title = (revision1 == null) ? g("CvsDiff.titleCVSHeadRevision") : g("CvsDiff.titleCVSRevision", revision1); // NOI18N
final String file2Title = (revision2 == null) ? g("CvsDiff.titleWorkingFile") : g("CvsDiff.titleCVSRevision", revision2); // NOI18N
javax.swing.SwingUtilities.invokeLater(new Runnable () {
public void run () {
if (tmpDir2 == null)
openEditors(tmpDir+/*File.separator+dir+*/File.separator+file,
rootDirWroot+File.separator+dir+File.separator+file,
file1Title, file2Title);
else
openEditors(tmpDir+/*File.separator+dir+*/File.separator+file,
tmpDir2+/*File.separator+dir+*/File.separator+file,
file1Title, file2Title);
diffFrame.setTitle("cvs diff: "+file); // NOI18N
diffFrame.repaint();
insertEmptyLines();
setDiffHighlight(true);
}
});
D.deb("exec return = "+status); // NOI18N
return status;
}
public int getNextDiffLine() {
currentDiffLine++;
if (currentDiffLine >= diffActions.size()) currentDiffLine = 0;
return ((DiffAction) diffActions.get(currentDiffLine)).getF1Line1();
}
public int getPrevDiffLine() {
currentDiffLine--;
if (currentDiffLine < 0) currentDiffLine = diffActions.size() - 1;
return ((DiffAction) diffActions.get(currentDiffLine)).getF1Line1();
}
/*
public void diffAgain() {
//setDiffHighlight(false);
diffFrame.unhighlightAll();
diffActions.removeAllElements();
performDiff();
setDiffHighlight(true);
}
*/
public void close() {
//new File(tmpDir, file).delete();
D.deb("deleting "+tmpDir); // NOI18N
MiscStuff.deleteRecursive(tmpDir);
if (tmpDir2 != null) {
D.deb("deleting "+tmpDir2); // NOI18N
MiscStuff.deleteRecursive(tmpDir2);
}
}
private boolean checkEmpty(String str, String element) {
if (str == null || str.length() == 0) {
if (this.stderrListener != null) {
String[] elements = { "Bad format of diff result: "+element }; // NOI18N
stderrListener.match(elements);
}
E.deb("Bad format of diff result: "+element); // NOI18N
return true;
}
return false;
}
public void match(String[] elements) {
diffBuffer.append(elements[0]+"\n"); // NOI18N
D.deb("diff match: "+elements[0]); // NOI18N
int index = 0, commaIndex = 0;
int n1 = 0, n2 = 0, n3 = 0, n4 = 0;
String nStr;
if ((index = elements[0].indexOf('a')) >= 0) {
DiffAction action = new DiffAction();
try {
n1 = Integer.parseInt(elements[0].substring(0, index));
index++;
commaIndex = elements[0].indexOf(',', index);
if (commaIndex < 0) {
nStr = elements[0].substring(index, elements[0].length());
if (checkEmpty(nStr, elements[0])) return;
n3 = Integer.parseInt(nStr);
n4 = n3;
} else {
nStr = elements[0].substring(index, commaIndex);
if (checkEmpty(nStr, elements[0])) return;
n3 = Integer.parseInt(nStr);
nStr = elements[0].substring(commaIndex+1, elements[0].length());
if (nStr == null || nStr.length() == 0) n4 = n3;
else n4 = Integer.parseInt(nStr);
}
} catch (NumberFormatException e) {
if (this.stderrListener != null) {
String[] debugOut = { "NumberFormatException "+e.getMessage() }; // NOI18N
stderrListener.match(debugOut);
}
E.deb("NumberFormatException "+e.getMessage()); // NOI18N
}
action.setAddAction(n1, n3, n4);
diffActions.add(action);
} else if ((index = elements[0].indexOf('d')) >= 0) {
DiffAction action = new DiffAction();
commaIndex = elements[0].lastIndexOf(',', index);
try {
if (commaIndex < 0) {
n1 = Integer.parseInt(elements[0].substring(0, index));
n2 = n1;
} else {
nStr = elements[0].substring(0, commaIndex);
if (checkEmpty(nStr, elements[0])) return;
n1 = Integer.parseInt(nStr);
nStr = elements[0].substring(commaIndex+1, index);
if (checkEmpty(nStr, elements[0])) return;
n2 = Integer.parseInt(nStr);
}
nStr = elements[0].substring(index+1, elements[0].length());
if (checkEmpty(nStr, elements[0])) return;
n3 = Integer.parseInt(nStr);
} catch (NumberFormatException e) {
if (this.stderrListener != null) {
String[] debugOut = { "NumberFormatException "+e.getMessage() }; // NOI18N
stderrListener.match(debugOut);
}
E.deb("NumberFormatException "+e.getMessage()); // NOI18N
}
action.setDeleteAction(n1, n2, n3);
diffActions.add(action);
} else if ((index = elements[0].indexOf('c')) >= 0) {
DiffAction action = new DiffAction();
commaIndex = elements[0].lastIndexOf(',', index);
try {
if (commaIndex < 0) {
n1 = Integer.parseInt(elements[0].substring(0, index));
n2 = n1;
} else {
nStr = elements[0].substring(0, commaIndex);
if (checkEmpty(nStr, elements[0])) return;
n1 = Integer.parseInt(nStr);
nStr = elements[0].substring(commaIndex+1, index);
if (checkEmpty(nStr, elements[0])) return;
n2 = Integer.parseInt(nStr);
}
index++;
commaIndex = elements[0].indexOf(',', index);
if (commaIndex < 0) {
nStr = elements[0].substring(index, elements[0].length());
if (checkEmpty(nStr, elements[0])) return;
n3 = Integer.parseInt(nStr);
n4 = n3;
} else {
nStr = elements[0].substring(index, commaIndex);
if (checkEmpty(nStr, elements[0])) return;
n3 = Integer.parseInt(nStr);
nStr = elements[0].substring(commaIndex+1, elements[0].length());
if (nStr == null || nStr.length() == 0) n4 = n3;
else n4 = Integer.parseInt(nStr);
}
} catch (NumberFormatException e) {
if (this.stderrListener != null) {
String[] debugOut = { "NumberFormatException "+e.getMessage() }; // NOI18N
stderrListener.match(debugOut);
}
E.deb("NumberFormatException "+e.getMessage()); // NOI18N
}
action.setChangeAction(n1, n2, n3, n4);
diffActions.add(action);
}
}
private String g(String s) {
D.deb("getting "+s);
return org.openide.util.NbBundle.getBundle(CvsDiff.class).getString(s);
}
private String g(String s, Object obj) {
return java.text.MessageFormat.format (g(s), new Object[] { obj });
}
private class DiffAction {
public static final int DELETE = 0;
public static final int CHANGE = 1;
public static final int ADD = 2;
private int action = 0;
private int f1Line1 = 0;
private int f1Line2 = 0;
private int f2Line1 = 0;
private int f2Line2 = 0;
public DiffAction() {
}
public void setDeleteAction(int f1Line1, int f1Line2, int f2Line1) {
this.action = DELETE;
this.f1Line1 = f1Line1;
this.f1Line2 = f1Line2;
this.f2Line1 = f2Line1;
}
public void setAddAction(int f1Line1, int f2Line1, int f2Line2) {
this.action = ADD;
this.f1Line1 = f1Line1;
this.f2Line1 = f2Line1;
this.f2Line2 = f2Line2;
}
public void setChangeAction(int f1Line1, int f1Line2, int f2Line1, int f2Line2) {
this.action = CHANGE;
this.f1Line1 = f1Line1;
this.f1Line2 = f1Line2;
this.f2Line1 = f2Line1;
this.f2Line2 = f2Line2;
}
public int getAction() {
return this.action;
}
public int getF1Line1() {
return this.f1Line1;
}
public void setF1Line1(int value) {
this.f1Line1 = value;
}
public int getF1Line2() {
return this.f1Line2;
}
public void setF1Line2(int value) {
this.f1Line2 = value;
}
public int getF2Line1() {
return this.f2Line1;
}
public void setF2Line1(int value) {
this.f2Line1 = value;
}
public int getF2Line2() {
return this.f2Line2;
}
public void setF2Line2(int value) {
this.f2Line2 = value;
}
}
}
/*
* Log
* 27 Gandalf-post-FCS1.25.1.0 3/29/00 Martin Entlicher Changed default tmp
* directory to system/vcs/tmp
* 26 Gandalf 1.25 1/18/00 Martin Entlicher
* 25 Gandalf 1.24 1/15/00 Ian Formanek NOI18N
* 24 Gandalf 1.23 1/6/00 Martin Entlicher
* 23 Gandalf 1.22 12/29/99 Martin Entlicher
* 22 Gandalf 1.21 12/28/99 Martin Entlicher
* 21 Gandalf 1.20 12/21/99 Martin Entlicher Changed to read the
* command from it's argument
* 20 Gandalf 1.19 12/14/99 Martin Entlicher Listeners added
* 19 Gandalf 1.18 12/8/99 Martin Entlicher
* 18 Gandalf 1.17 12/2/99 Martin Entlicher
* 17 Gandalf 1.16 11/11/99 Martin Entlicher checkout for diff
* changed to non-absolut pathname
* 16 Gandalf 1.15 11/10/99 Martin Entlicher
* 15 Gandalf 1.14 11/9/99 Martin Entlicher
* 14 Gandalf 1.13 11/9/99 Martin Entlicher
* 13 Gandalf 1.12 11/4/99 Martin Entlicher
* 12 Gandalf 1.11 11/2/99 Martin Entlicher
* 11 Gandalf 1.10 10/27/99 Martin Entlicher Checkout fixed for Win
* NT
* 10 Gandalf 1.9 10/26/99 Martin Entlicher
* 9 Gandalf 1.8 10/26/99 Martin Entlicher
* 8 Gandalf 1.7 10/26/99 Martin Entlicher
* 7 Gandalf 1.6 10/25/99 Pavel Buzek
* 6 Gandalf 1.5 10/23/99 Ian Formanek NO SEMANTIC CHANGE - Sun
* Microsystems Copyright in File Comment
* 5 Gandalf 1.4 10/13/99 Martin Entlicher
* 4 Gandalf 1.3 10/9/99 Pavel Buzek
* 3 Gandalf 1.2 10/9/99 Martin Entlicher
* 2 Gandalf 1.1 10/9/99 Martin Entlicher Added SERVERTYPE
* variable
* 1 Gandalf 1.0 10/7/99 Martin Entlicher initial revision
* $
*/